baubs git
Commit b2f0455d4cf51a7aef633bbab15c7614cd82d53a
Parents : a828bf4
Author : copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Date : 2025-12-27T16:58:19Z
feature: Refactor ErrorPopover and StepPopover to use InfoCard
Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
Changes
4 files changed, 108 insertions(+), 180 deletions(-)
Diff
diff --git a/src/components/ErrorPopover.tsx b/src/components/ErrorPopover.tsx
index 17d11e4..282f26f 100644
--- a/src/components/ErrorPopover.tsx
+++ b/src/components/ErrorPopover.tsx
@@ -1,10 +1,13 @@
-import {
- ExclamationTriangleIcon,
- InformationCircleIcon,
-} from "@heroicons/react/24/solid";
import { getErrorDetails } from "../utils/errorCodeHelpers";
import { PopoverContent } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
+import {
+ InfoCard,
+ InfoCardTitle,
+ InfoCardDescription,
+ InfoCardList,
+ InfoCardListItem,
+} from "@/components/ui/info-card";
interface ErrorPopoverContentProps {
machineError?: number;
@@ -24,71 +27,50 @@ export function ErrorPopoverContent({
const errorMsg = pyodideError || errorMessage || "";
const isInfo = isPairingErr || errorDetails?.isInformational;
- const bgColor = isInfo
- ? "bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500"
- : "bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500";
-
- const iconColor = isInfo
- ? "text-info-600 dark:text-info-400"
- : "text-danger-600 dark:text-danger-400";
-
- const textColor = isInfo
- ? "text-info-900 dark:text-info-200"
- : "text-danger-900 dark:text-danger-200";
-
- const descColor = isInfo
- ? "text-info-800 dark:text-info-300"
- : "text-danger-800 dark:text-danger-300";
-
- const listColor = isInfo
- ? "text-info-700 dark:text-info-300"
- : "text-danger-700 dark:text-danger-300";
-
- const Icon = isInfo ? InformationCircleIcon : ExclamationTriangleIcon;
+ const variant = isInfo ? "info" : "error";
const title =
errorDetails?.title || (isPairingErr ? "Pairing Required" : "Error");
return (
- <PopoverContent
- className={cn("w-[600px] border-l-4 p-4 backdrop-blur-sm", bgColor)}
- align="start"
- >
- <div className="flex items-start gap-3">
- <Icon className={cn("w-6 h-6 flex-shrink-0 mt-0.5", iconColor)} />
- <div className="flex-1">
- <h3 className={cn("text-base font-semibold mb-2", textColor)}>
- {title}
- </h3>
- <p className={cn("text-sm mb-3", descColor)}>
- {errorDetails?.description || errorMsg}
+ <PopoverContent className="w-[600px] p-0" align="start">
+ <InfoCard variant={variant} className="rounded-lg shadow-none border-0">
+ <InfoCardTitle variant={variant}>{title}</InfoCardTitle>
+ <InfoCardDescription variant={variant}>
+ {errorDetails?.description || errorMsg}
+ </InfoCardDescription>
+ {errorDetails?.solutions && errorDetails.solutions.length > 0 && (
+ <>
+ <h4
+ className={cn(
+ "text-sm font-semibold mb-2",
+ variant === "info"
+ ? "text-info-900 dark:text-info-200"
+ : "text-danger-900 dark:text-danger-200",
+ )}
+ >
+ {isInfo ? "Steps:" : "How to Fix:"}
+ </h4>
+ <InfoCardList variant={variant} ordered>
+ {errorDetails.solutions.map((solution, index) => (
+ <InfoCardListItem key={index}>{solution}</InfoCardListItem>
+ ))}
+ </InfoCardList>
+ </>
+ )}
+ {machineError !== undefined && !errorDetails?.isInformational && (
+ <p
+ className={cn(
+ "text-xs mt-3 font-mono",
+ variant === "info"
+ ? "text-info-800 dark:text-info-300"
+ : "text-danger-800 dark:text-danger-300",
+ )}
+ >
+ Error Code: 0x
+ {machineError.toString(16).toUpperCase().padStart(2, "0")}
</p>
- {errorDetails?.solutions && errorDetails.solutions.length > 0 && (
- <>
- <h4 className={cn("text-sm font-semibold mb-2", textColor)}>
- {isInfo ? "Steps:" : "How to Fix:"}
- </h4>
- <ol
- className={cn(
- "list-decimal list-inside text-sm space-y-1.5",
- listColor,
- )}
- >
- {errorDetails.solutions.map((solution, index) => (
- <li key={index} className="pl-2">
- {solution}
- </li>
- ))}
- </ol>
- </>
- )}
- {machineError !== undefined && !errorDetails?.isInformational && (
- <p className={cn("text-xs mt-3 font-mono", descColor)}>
- Error Code: 0x
- {machineError.toString(16).toUpperCase().padStart(2, "0")}
- </p>
- )}
- </div>
- </div>
+ )}
+ </InfoCard>
</PopoverContent>
);
}
diff --git a/src/components/WorkflowStepper/StepPopover.tsx b/src/components/WorkflowStepper/StepPopover.tsx
index d625fae..741f075 100644
--- a/src/components/WorkflowStepper/StepPopover.tsx
+++ b/src/components/WorkflowStepper/StepPopover.tsx
@@ -5,12 +5,15 @@
*/
import { forwardRef } from "react";
-import {
- InformationCircleIcon,
- ExclamationTriangleIcon,
-} from "@heroicons/react/24/solid";
import { MachineStatus } from "../../types/machine";
import { getGuideContent } from "../../utils/workflowGuideContent";
+import {
+ InfoCard,
+ InfoCardTitle,
+ InfoCardDescription,
+ InfoCardList,
+ InfoCardListItem,
+} from "@/components/ui/info-card";
export interface StepPopoverProps {
stepId: number;
@@ -22,54 +25,16 @@ export const StepPopover = forwardRef<HTMLDivElement, StepPopoverProps>(
const content = getGuideContent(stepId, machineStatus);
if (!content) return null;
- const colorClasses = {
- info: "bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500",
- success:
- "bg-success-50 dark:bg-success-900/95 border-success-600 dark:border-success-500",
- warning:
- "bg-warning-50 dark:bg-warning-900/95 border-warning-600 dark:border-warning-500",
- error:
- "bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500",
- progress:
- "bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500",
- };
-
- const iconColorClasses = {
- info: "text-info-600 dark:text-info-400",
- success: "text-success-600 dark:text-success-400",
- warning: "text-warning-600 dark:text-warning-400",
- error: "text-danger-600 dark:text-danger-400",
- progress: "text-info-600 dark:text-info-400",
- };
-
- const textColorClasses = {
- info: "text-info-900 dark:text-info-200",
- success: "text-success-900 dark:text-success-200",
- warning: "text-warning-900 dark:text-warning-200",
- error: "text-danger-900 dark:text-danger-200",
- progress: "text-info-900 dark:text-info-200",
- };
-
- const descColorClasses = {
- info: "text-info-800 dark:text-info-300",
- success: "text-success-800 dark:text-success-300",
- warning: "text-warning-800 dark:text-warning-300",
- error: "text-danger-800 dark:text-danger-300",
- progress: "text-info-800 dark:text-info-300",
- };
-
- const listColorClasses = {
- info: "text-blue-700 dark:text-blue-300",
- success: "text-green-700 dark:text-green-300",
- warning: "text-yellow-700 dark:text-yellow-300",
- error: "text-red-700 dark:text-red-300",
- progress: "text-cyan-700 dark:text-cyan-300",
- };
+ // Map content type to InfoCard variant
+ const variantMap = {
+ info: "info",
+ success: "success",
+ warning: "warning",
+ error: "error",
+ progress: "info",
+ } as const;
- const Icon =
- content.type === "warning"
- ? ExclamationTriangleIcon
- : InformationCircleIcon;
+ const variant = variantMap[content.type];
return (
<div
@@ -78,45 +43,30 @@ export const StepPopover = forwardRef<HTMLDivElement, StepPopoverProps>(
role="dialog"
aria-label="Step guidance"
>
- <div
- className={`${colorClasses[content.type]} border-l-4 p-4 rounded-lg shadow-xl backdrop-blur-sm`}
- >
- <div className="flex items-start gap-3">
- <Icon
- className={`w-6 h-6 ${iconColorClasses[content.type]} flex-shrink-0 mt-0.5`}
- />
- <div className="flex-1">
- <h3
- className={`text-base font-semibold ${textColorClasses[content.type]} mb-2`}
- >
- {content.title}
- </h3>
- <p className={`text-sm ${descColorClasses[content.type]} mb-3`}>
- {content.description}
- </p>
- {content.items && content.items.length > 0 && (
- <ul
- className={`list-disc list-inside text-sm ${listColorClasses[content.type]} space-y-1`}
- >
- {content.items.map((item, index) => {
- // Parse **text** markdown syntax into React elements safely
- const parts = item.split(/(\*\*.*?\*\*)/);
- return (
- <li key={index} className="pl-2">
- {parts.map((part, i) => {
- if (part.startsWith("**") && part.endsWith("**")) {
- return <strong key={i}>{part.slice(2, -2)}</strong>;
- }
- return part;
- })}
- </li>
- );
- })}
- </ul>
- )}
- </div>
- </div>
- </div>
+ <InfoCard variant={variant} className="shadow-xl">
+ <InfoCardTitle variant={variant}>{content.title}</InfoCardTitle>
+ <InfoCardDescription variant={variant}>
+ {content.description}
+ </InfoCardDescription>
+ {content.items && content.items.length > 0 && (
+ <InfoCardList variant={variant}>
+ {content.items.map((item, index) => {
+ // Parse **text** markdown syntax into React elements safely
+ const parts = item.split(/(\*\*.*?\*\*)/);
+ return (
+ <InfoCardListItem key={index}>
+ {parts.map((part, i) => {
+ if (part.startsWith("**") && part.endsWith("**")) {
+ return <strong key={i}>{part.slice(2, -2)}</strong>;
+ }
+ return part;
+ })}
+ </InfoCardListItem>
+ );
+ })}
+ </InfoCardList>
+ )}
+ </InfoCard>
</div>
);
},
diff --git a/src/components/ui/info-card.test.tsx b/src/components/ui/info-card.test.tsx
index 637dcff..94ef41b 100644
--- a/src/components/ui/info-card.test.tsx
+++ b/src/components/ui/info-card.test.tsx
@@ -31,7 +31,8 @@ describe("InfoCard", () => {
);
expect(screen.getByText(`${variant} Card`)).toBeTruthy();
- expect(container.firstChild?.classList.contains("border-l-4")).toBe(true);
+ const firstChild = container.firstChild as HTMLElement | null;
+ expect(firstChild?.classList.contains("border-l-4")).toBe(true);
});
});
@@ -77,9 +78,7 @@ describe("InfoCard", () => {
describe("InfoCardDescription", () => {
it("should render description text", () => {
- render(
- <InfoCardDescription>This is a description</InfoCardDescription>,
- );
+ render(<InfoCardDescription>This is a description</InfoCardDescription>);
expect(screen.getByText("This is a description")).toBeTruthy();
});
@@ -150,9 +149,7 @@ describe("InfoCard", () => {
);
expect(screen.getByText("Success!")).toBeTruthy();
- expect(
- screen.getByText("Operation completed successfully"),
- ).toBeTruthy();
+ expect(screen.getByText("Operation completed successfully")).toBeTruthy();
expect(screen.getByText("First step completed")).toBeTruthy();
expect(screen.getByText("Second step completed")).toBeTruthy();
});
diff --git a/src/components/ui/info-card.tsx b/src/components/ui/info-card.tsx
index a2b7bee..3d33856 100644
--- a/src/components/ui/info-card.tsx
+++ b/src/components/ui/info-card.tsx
@@ -8,25 +8,22 @@ import {
import { cn } from "@/lib/utils";
-const infoCardVariants = cva(
- "border-l-4 p-4 rounded-lg backdrop-blur-sm",
- {
- variants: {
- variant: {
- info: "bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500",
- warning:
- "bg-warning-50 dark:bg-warning-900/95 border-warning-600 dark:border-warning-500",
- error:
- "bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500",
- success:
- "bg-success-50 dark:bg-success-900/95 border-success-600 dark:border-success-500",
- },
- },
- defaultVariants: {
- variant: "info",
+const infoCardVariants = cva("border-l-4 p-4 rounded-lg backdrop-blur-sm", {
+ variants: {
+ variant: {
+ info: "bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500",
+ warning:
+ "bg-warning-50 dark:bg-warning-900/95 border-warning-600 dark:border-warning-500",
+ error:
+ "bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500",
+ success:
+ "bg-success-50 dark:bg-success-900/95 border-success-600 dark:border-success-500",
},
},
-);
+ defaultVariants: {
+ variant: "info",
+ },
+});
const iconColorVariants = cva("w-6 h-6 flex-shrink-0 mt-0.5", {
variants: {
@@ -85,7 +82,8 @@ const listColorVariants = cva("text-sm", {
});
interface InfoCardProps
- extends React.HTMLAttributes<HTMLDivElement>,
+ extends
+ React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof infoCardVariants> {
icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
showDefaultIcon?: boolean;
@@ -145,7 +143,8 @@ function InfoCardDescription({
}
interface InfoCardListProps
- extends React.HTMLAttributes<HTMLOListElement | HTMLUListElement>,
+ extends
+ React.HTMLAttributes<HTMLOListElement | HTMLUListElement>,
VariantProps<typeof listColorVariants> {
ordered?: boolean;
}
Served by rngit 1.3.3 - Generated in 0.06s